package part3;

/******************************************************
 Laboratoire #3 : Programmation d'un serveur DNS

 Cours :             LOG610
 Session :           Hiver 2007
 Groupe :            01
 Projet :            Laboratoire #3
 �tudiant(e)(s) :    Maxime Bouchard
 Code(s) perm. :     BOUM24028309

 Professeur :        Michel Lavoie 
 Nom du fichier :    UDPReceiver.java
 Date cr�e :         2007-03-10
 Date dern. modif.   X
 *******************************************************/
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * Cette classe permet la r�ception d'un paquet UDP sur le port de r�ception
 * UDP/DNS. Elle analyse le paquet et extrait le hostname
 * 
 * Il s'agit d'un Thread qui �coute en permanance pour ne pas affecter le
 * d�roulement du programme
 * 
 * @author Max
 * 
 */

public class UDPReceiver extends Thread {
    /**
     * Les champs d'un Packet UDP -------------------------- En-t�te (12
     * octects) Question : l'adresse demand� R�ponse : l'adresse IP Autorit� :
     * info sur le serveur d'autorit� Additionnel : information suppl�mentaire
     */

    /**
     * D�finition de l'En-t�te d'un Packet UDP
     * --------------------------------------- Identifiant Param�tres QDcount
     * Ancount NScount ARcount
     * 
     * � identifiant est un entier permettant d�identifier la requete. �
     * parametres contient les champs suivant : � QR (1 bit) : indique si le
     * message est une question (0) ou une reponse (1). � OPCODE (4 bits) : type
     * de la requete (0000 pour une requete simple). � AA (1 bit) : le serveur
     * qui a fourni la reponse a-t�il autorite sur le domaine? � TC (1 bit) :
     * indique si le message est tronque. � RD (1 bit) : demande d�une requete
     * recursive. � RA (1 bit) : indique que le serveur peut faire une demande
     * recursive. � UNUSED, AD, CD (1 bit chacun) : non utilises. � RCODE (4
     * bits) : code de retour. 0 : OK, 1 : erreur sur le format de la requete,
     * 2: probleme du serveur, 3 : nom de domaine non trouve (valide seulement
     * si AA), 4 : requete non supportee, 5 : le serveur refuse de repondre
     * (raisons de s�ecurite ou autres). � QDCount : nombre de questions. �
     * ANCount, NSCount, ARCount : nombre d�entrees dans les champs �Reponse�,
     * �Autorite�, �Additionnel�.
     */

    /**
     * Les champs Reponse, Autorite, Additionnel sont tous representes de la
     * meme maniere :
     * 
     * � Nom (16 bits) : Pour eviter de recopier la totalite du nom, on utilise
     * des offsets. Par exemple si ce champ vaut C0 0C, cela signifie qu�on a un
     * offset (C0) de 12 (0C) octets. C�est-a-dire que le nom en clair se trouve
     * au 12eme octet du message. � Type (16 bits) : idem que pour le champ
     * Question. � Class (16 bits) : idem que pour le champ Question. � TTL (32
     * bits) : dur�ee de vie de l�entr�ee. � RDLength (16 bits): nombre d�octets
     * de la zone RDData. � RDData (RDLength octets) : reponse
     */

    private DataInputStream d = null;
    protected final static int BUF_SIZE = 1024;
    protected String SERVER_DNS = null;
    protected int port = 53; // port de r�ception
    private String DomainName = "none";
    private String DNSFile = null;
    private String adrIP = null;
    private boolean RedirectionSeulement = false;
    private String adresseIP = null;

    public void setport(int p) {
	this.port = p;
    }

    public void setRedirectionSeulement(boolean b) {
	this.RedirectionSeulement = b;
    }

    public String gethostNameFromPacket() {
	return DomainName;
    }

    public String getAdrIP() {
	return adrIP;
    }

    private void setAdrIP(String ip) {
	adrIP = ip;
    }

    public void sethostNameFromPacket(String hostname) {
	this.DomainName = hostname;
    }

    public String getSERVER_DNS() {
	return SERVER_DNS;
    }

    public void setSERVER_DNS(String server_dns) {
	this.SERVER_DNS = server_dns;
    }

    public UDPReceiver(String server_dns, int Port) {
	this.SERVER_DNS = server_dns;
	this.port = Port;
    }

    public UDPReceiver() {
    }

    public void setDNSFile(String filename) {
	DNSFile = filename;
    }

    public void run() {

	try {

	    byte[] data = new byte[BUF_SIZE];
	    // *Creation d'un socket UDP
	    DatagramSocket socket = new DatagramSocket(this.port);
	    DatagramPacket packet = new DatagramPacket(data, data.length);
	    // *Boucle infinie de recpetion
	    while (true) {

		// *Reception d'un paquet UDP via le socket
		socket.receive(packet);
		// *Creation d'un DataInputStream ou ByteArrayInputStream pour
		// manipuler les bytes du paquet
		ByteArrayInputStream is = new ByteArrayInputStream(
			packet.getData());
		// *Lecture et sauvegarde des deux premier bytes, qui specifie
		// l'identifiant

		int reqId = is.read() + is.read();
		System.out.println("Request Id: " + reqId);
		// *Lecture et sauvegarde du huitieme byte, qui specifie le
		// nombre de reponse dans le message
		is.skip(5);
		int nbRep = is.read();
		System.out.println("nbRep: " + nbRep);
		is.skip(4);		

		// *Dans le cas d'une reponse
		if (nbRep > 0) {
		    //TODO le rendre compatible avec les adresse qui on plus de 2 parti (ex : abc.xyz.com)
		 // *Lecture du Query Domain name, a partir du 13 byte
		    int lngDomain = is.read();
		    for (int i = 0; i < lngDomain; i++) {
			DomainName += new String(Character.toChars(is.read()));
		    }

		    DomainName += ".";

		    lngDomain = is.read();
		    for (int i = 0; i < lngDomain; i++) {
			DomainName += new String(Character.toChars(is.read()));
		    }
		    // *Sauvegarde du Query Domain name
		    System.out.println(DomainName);
		    
		    // *Passe par dessus Query Type et Query Class
		    // *Passe par dessus les premiers champs du ressource record
		    // pour arriver au ressource data
		    // *qui contient l'adresse IP associe au hostname (dans le
		    // fond saut de 16 bytes)
		    is.skip(16);
		    // *Capture de l'adresse IP
		    this.adresseIP = "";
		    for(int i=0;i<3;i++){
			adresseIP += is.read();
			if(i != 3){
			    adresseIP += ".";
			}
		    }
		    // *Ajouter la correspondance dans le fichier seulement si
		    // une seule
		    // *reponse dans le message DNS (cette apllication ne traite
		    // que ce cas)
		    String recherche = new QueryFinder(this.DNSFile,this.DomainName).StartResearch(this.DomainName);
		    if(recherche.equals(null))
			recherche = "none";		    
		    
		    // *Faire parvenir le paquet reponse au demandeur original,
		    // ayant emis une requete
		    // *avec cet identifiant
		    byte[] reponse = new UDPAnswerPacketCreator().CreateAnswerPacket(data, adresseIP);
		    DatagramPacket packetReponse = new DatagramPacket(reponse, reponse.length);
		    UDPSender sender = new UDPSender(packetReponse,"127.0.0.1",port);
		    sender.setSocket(socket);
		    sender.SendPacketNow();
		    
		    // *Dans le cas d'une requete
		} else if (nbRep == 0) {
		    int lngDomain = is.read();
		    System.out.println("longDomain: " + lngDomain);
		    DomainName = "";
		    // *Lecture du Query Domain name, a partir du 13 byte
		    for (int i = 0; i < lngDomain; i++) {
			DomainName += new String(Character.toChars(is.read()));
		    }

		    DomainName += ".";

		    lngDomain = is.read();
		    for (int i = 0; i < lngDomain; i++) {
			DomainName += new String(Character.toChars(is.read()));
		    }
		    // *Sauvegarde du Query Domain name
		    System.out.println(DomainName);
		    // *Sauvegarde de l'adresse, du port et de l'identifiant de
		    // la
		    // requete
		  //  this.adresseIP = packet.getAddress().toString()
			//    .substring(1);
		    
		    InetAddress adressetemp = packet.getAddress();
		    this.adresseIP = adressetemp.getHostAddress();
		    int numPort = packet.getPort();

		    // *Si le mode est redirection seulement
		    if (RedirectionSeulement) {
			// *Rediriger le paquet vers le serveur DNS
			UDPSender sender = new UDPSender();
			byte[] retour = new byte[4];

			String[] split = SERVER_DNS.split("\\.");

			for (int i = 0; i < retour.length; i++) {
			    retour[i] = (byte) Integer.parseInt(split[0]);
			}

			InetAddress address = InetAddress.getByAddress(retour);
			packet.setAddress(address);
			sender.setPacket(packet);
			sender.SendPacketNow();
			// *Sinon
		    } else {
			// *Rechercher l'adresse IP associe au Query Domain name
			// dans le
			// fichier de
			// *correspondance de ce serveur
			QueryFinder finder = new QueryFinder(this.DNSFile,
				this.SERVER_DNS);
			// *Si la correspondance n'est pas trouvee
			String valeur = finder.StartResearch(this.DomainName);			
			if (valeur.equals("none")) {
			 // *Rediriger le paquet vers le serveur DNS
			    UDPSender sender = new UDPSender(packet,this.SERVER_DNS,this.port);
			    sender.setPacket(packet);
			    sender.SendPacketNow();
			}else{
			 // *Sinon
			// *Creer le paquet de reponse a l'aide du
			// UDPAnswerPaquetCreator
			    byte[] reponse = new UDPAnswerPacketCreator().CreateAnswerPacket(packet.getData(), valeur);
			// *Placer ce paquet dans le socket
			    DatagramPacket packetRetour = new DatagramPacket(reponse, reponse.length);
			    UDPSender sender = new UDPSender(this.adresseIP,numPort);
			    sender.setPacket(packetRetour);
			    sender.setSocket(socket);
			// *Envoyer le paquet
			    sender.SendPacketNow();
			}
			
		    }
		}

	    }
	} catch (Exception e) {
	    System.err.println("Probl�me � l'ex�cution :");
	    e.printStackTrace(System.err);
	}

    }
}
